import sys
import traceback
from typing import Optional

from installer.DiscoveryStandalone import DiscoveryStandalone



from .TimeSync import TimeSync
from .Config import Config
from .Logging import Logger
from .Service import Service
from .Context import Context, OperationMode
from .Discovery import Discovery
from .Configure import Configure
from .Permissions import Permissions
from .Uninstall import Uninstall

class Installer:

    def Run(self):
        try:
            # Any error during the process will be thrown, which will be printed here, and exit the installer.
            self._run()
        except Exception as e:
            tb = traceback.format_exc()
            Logger.Blank()
            Logger.Blank()
            Logger.Error("Installer failed - "+str(e))
            Logger.Blank()
            Logger.Blank()
            Logger.Error("Stack Trace:")
            Logger.Error(str(tb))
            Logger.Blank()
            Logger.Blank()
            Logger.Header("Please contact our support team directly at help@mobileraker.com so we can help you fix this issue!")
            Logger.Blank()
            Logger.Blank()


    def _run(self):

        #
        # Setup Phase
        #
        
        # The installer script passes a json object to us, which contains all of the args.
        # But it might not be the first arg.
        json_args = self._parse_args()
        if json_args is None:
            raise AttributeError("Failed to find cmd line json arg")

        # Parse and validate the args.
        context = Context.setup(json_args)

        # As soon as we have the user home make the log file.
        Logger.setup(context.user_home)

        Logger.Info("Starting Installer...")

        # Parse the original CmdLineArgs
        Logger.Debug("Parsing install (bash) cmd line args.")
        context.parse_bash_args()

        # Figure out the OS type we are installing on.
        # This can be a normal debian device, Sonic Pad, K1, or others.
        context.identify_platform()
        Logger.Info(f"Os Type Detected: {context.platform}")

        # Print this again now that the debug cmd flag is parsed, since it might be useful.
        if context.debug:
            Logger.Debug("Found config: "+json_args)

        # Before we do the first validation, make sure the User var is setup correctly and update if needed.
        permissions = Permissions()
        permissions.ensure_valid_username_for_root_installation(context)

        # Validate we have the required args, but not the moonraker values yet, since they are optional.
        # All generation 1 vars must exist and be valid.
        Logger.Debug("Validating args")
        context.validate_phase_one()

        #
        # Run Phase
        #

        # If the help flag is set, do that now and exit.
        if context.show_help:
            # If we should show help, do it now and return.
            self.print_help()
            return
        
        # Ensure that the system clock sync is enabled. For some MKS PI systems the OS time is wrong and sync is disabled.
        # The user would of had to manually correct the time to get this installer running, but we will ensure that the
        # time sync systemd service is enabled to keep the clock in sync after reboots, otherwise it will cause SSL errors.
        TimeSync.ensure_ntp_sync_enabled(context)

        # Ensure the script at least has sudo permissions.
        # It's required to set file permission and to write / restart the service.
        # See comments in the function for details.
        permissions.validate_root_privileges(context)

        # # If we are in update mode, do the update logic and exit.
        # if context.mode == OperationMode.UPDATE:
        #     # TODO: Adjust Update to use the new context.
        #     # Before the update, make sure all permissions are set
        #     # correctly.
        #     permissions.validate_context_permissions(context)

        #     # Do the update logic.
        #     update = Updater()
        #     update.DoUpdate(context)
        #     return

        # If we are running as an uninstaller, run that logic and exit.
        if context.mode == OperationMode.UNINSTALL:
            # TODO: Adjust Uninstall to use the new context.
            uninstall = Uninstall()
            uninstall.uninstall(context)
            return
        
        # Next step is to find the moonraker files.
        if context.is_standalone:
            discovery = DiscoveryStandalone()
            discovery.start(context)
        else:
            discovery = Discovery()
            discovery.start(context)

        # Validate the response.
        # TODO: Maybe discovery should also detect if a comapnion instance exists?
        # All generation 2 values must be set and valid.
        context.validate_phase_two()

        # Next, based on the vars generated by discovery, complete the configuration of the context.
        configure = Configure()
        configure.run(context)

        config = Config()
        config.run(context)

        # After configuration, gen 3 should be fully valid.
        context.validate_phase_three()

        # Just before we start (or restart) the service, ensure all of the permission are set correctly
        permissions.validate_context_permissions(context)

        # We are fully configured, create the service file and it's dependent files.
        service = Service()
        service.install(context)

        # Success!
        Logger.Blank()
        Logger.Blank()
        Logger.Blank()
        Logger.Purple("  ~~~ Mobileraker Companion installation Complete ~~~")
        Logger.Header("                   Happy Printing!")
        Logger.Error( "                          <3")
        Logger.Blank()
        Logger.Blank()


    def _parse_args(self) -> Optional[str]:
        # We want to skip arguments until we find the json string and then concat all args after that together.
        # The reason is the PY args logic will split the entire command line string by space, so any spaces in the json get broken
        # up into different args. This only really happens in the case of the CMD_LINE_ARGS, since it can be like "-companion -debug -whatever"
        json_args = None
        for arg in sys.argv:
            # Find the json start.
            if len(arg) > 0 and arg[0] == '{':
                json_args = arg
            # Once we have started a json string, keep building it.
            elif json_args is not None:
                # We need to add the space back to make up for the space removed during the args split.
                json_args += " " + arg
        return json_args


    def print_help(self):
        Logger.Blank()
        Logger.Blank()
        Logger.Blank()
        Logger.Blank()
        Logger.Header("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        Logger.Header("    Mobileraker Companion Setup     ")
        Logger.Header("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        Logger.Blank()
        Logger.Info("This installer script helps you to setup mobileraker on your klipperized 3D printer.")
        Logger.Blank()
        Logger.Warn("Command line format:")
        Logger.Info("  <moonraker config file path> <moonraker service file path> -other -args")
        Logger.Blank()
        Logger.Warn("Argument details:")
        Logger.Info("  <moonraker config file path>  - optional - If supplied, the install will target this moonraker setup without asking or searching for others")
        Logger.Info("  <moonraker service name> - optional - If supplied, the install will target this moonraker service file without searching.")
        Logger.Info("       Used when multiple moonraker instances are ran on the same device. Typically the file name is something like `moonraker-1.service` or `moonraker-somename.service`")
        Logger.Blank()
        Logger.Warn("Other Optional Args:")
        Logger.Info("  -help            - Shows this message.")
        Logger.Info("  -noatuoselect    - Disables auto selecting a moonraker instance, allowing the user to always choose.")
        Logger.Info("  -debug           - Enable debug logging to the console.")
        Logger.Info("  -skipsudoactions - Skips sudo required actions. This is useful for debugging, but will make the install not fully work.")
        Logger.Blank()